﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apache.NMS.ActiveMQ.Commands;
using Spring.Messaging.Nms.Core;
using Apache.NMS.ActiveMQ;
using System.Windows.Controls;
using System.Web.Services.Protocols;
using System.Net;

namespace ModeloGaleriaNET
{
    delegate void AddDataGridColumn();
    public class GestorMensajes
    {
        private enum MOMENTOAUDITORIA { INICIO, CORRECTO, LAST_ERROR };
        private Queue<IMessageType> peticiones;
        private Queue<ActiveMQMapMessage> peticionesMQ;
        private AvalonDock.DockableContent doc;
        private bool stop;
        /// <summary>
        /// Cola de mensajes de entrada de ActiveMQ. La llave es la ref externa y el contenido el mensaje
        /// </summary>
        private Dictionary<long, ActiveMQMapMessage> activeMQEntrada;

        private GestorMensajes()
        {
            peticiones = new Queue<IMessageType>();
            peticionesMQ = new Queue<ActiveMQMapMessage>();
            activeMQEntrada = new Dictionary<long, ActiveMQMapMessage>();
        }

        public static readonly GestorMensajes Instance = new GestorMensajes();

        public AvalonDock.DockableContent Documento
        {
            set { doc = value; }
        }

        public bool Stop
        {
            set { stop = value; }
        }

        private bool calljUDDI(bool uddi)
        {
            if (!uddi)
            {
                Utilities.WriteMessageOnGrid(doc, "Solicitando dirección del servidor a jUDDI...", DataGridMessage.TIPO_MENSAJE.INFO);
                jUDDIManager.Instance.UpdateURL();
            }
            return true;
        }

        private void catchException(Exception exception)
        {
            Exception e;
            
            e = exception;
            while (e != null)
            {
                Utilities.WriteMessageOnGrid(doc, e.Message, DataGridMessage.TIPO_MENSAJE.WARNING);
                e = e.InnerException;
            }
        }

        public void sendMessages()
        {
            IMessageType mensaje = null;
            int intentos;
            int salida;
            bool uddiPedido;
            string descripcion = string.Empty, mens = string.Empty;
            int usuario = -1, tipo = -1;

            while (peticiones.Count > 0)
            {
                uddiPedido = false;
                lock (this.peticiones) {
                    mensaje = peticiones.Dequeue();
                }

                intentos = 0;
                // Mensaje de inicio de una petición
                mensaje.printInitialMessage();
                SetMessageParameters(MOMENTOAUDITORIA.INICIO, mensaje.GetType(), mensaje, out tipo, out descripcion, out usuario);
                InsertAuditoriaDB("insert into auditorias (fecha, descripcion, tipo, zonaHoraria, usuario, ip) values ('" +
                            String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.UtcNow) + "', '" + descripcion + "', " + tipo +
                            ", " + Utilities.GetZonaHoraria() + ", " + usuario + ", '" + Utilities.GetLocalIP() + "');");
                
                while (true)
                {
                    try
                    {
                        salida = mensaje.Execute();
                        #region Mensajes y BD local
                        if (mensaje is MessageCrearSubasta)
                        {
                            // Me paso la abstracción por el culete..
                            ((MessageCrearSubasta)mensaje).Subasta.ReferenciaExterna = salida.ToString();
                            GaleriaContext.Instance.Contexto.SaveChanges();
                        }
                        mensaje.printSuccessMessage();
                        SetMessageParameters(MOMENTOAUDITORIA.CORRECTO, mensaje.GetType(), mensaje, out tipo, out descripcion, out usuario);
                        InsertAuditoriaDB("insert into auditorias (fecha, descripcion, tipo, zonaHoraria, usuario, ip) values ('" +
                                    String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.UtcNow) + "', '" + descripcion + "', " + tipo +
                                    ", " + Utilities.GetZonaHoraria() + ", " + usuario + ", '" + Utilities.GetLocalIP() + "');");
                        #endregion
                        break;
                    }
                    catch (WebException ex)
                    {
                        catchException(ex);
                        uddiPedido = calljUDDI(uddiPedido);
                        intentos++;
                        System.Threading.Thread.Sleep(5000);
                        if (intentos == 3)
                            break;
                    }
                    catch (SoapException ex)
                    {
                        if (ex.Message.Equals("Pseudónimo o contraseña incorrectos."))
                        {
                            Utilities.WriteMessageOnGrid(doc,
                                "Usuario o contraseña del servidor incorrectos", DataGridMessage.TIPO_MENSAJE.ERROR);
                            RollBack(mensaje);
                            break;
                        }
                        else
                            catchException(ex);
                        intentos++;
                        if (intentos >= 3)
                            break;
                    }
                    catch (Exception ex)
                    {
                        catchException(ex);
                        intentos++;
                        System.Threading.Thread.Sleep(5000);
                        if (intentos == 3)
                            break;
                    }
                }

                if (intentos == 3) // No se ha podido mandar el mensaje en 3 intentos
                {
                    #region Deshacer cambios en local y notificar
                    RollBack(mensaje);
                    #endregion
                    mensaje.printLastErrorMessage();
                    SetMessageParameters(MOMENTOAUDITORIA.LAST_ERROR, mensaje.GetType(), mensaje, out tipo, out descripcion, out usuario);
                    InsertAuditoriaDB("insert into auditorias (fecha, descripcion, tipo, zonaHoraria, usuario, ip) values ('" +
                                String.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.UtcNow) + "', '" + descripcion + "', " + tipo +
                                ", " + Utilities.GetZonaHoraria() + ", " + usuario + ", '" + Utilities.GetLocalIP() + "');");
                }
            }
        }

        private void SetMessageParameters(MOMENTOAUDITORIA momento, Type tipoMensaje, IMessageType mensaje, out int tipo, out string descripcion, out int usuario)
        {
            string mens;
            int tipoIn;
            
            tipoIn = tipo = 0;
            switch (momento)
            {
                case MOMENTOAUDITORIA.INICIO:
                    if (tipoMensaje == typeof(MessageCrearSubasta))
                        tipoIn = tipo = 41;
                    else if (tipoMensaje == typeof(ModificarSubastaMessage))
                        tipoIn = tipo = 62;
                    else if (tipoMensaje == typeof(FinalizarSubastaMessage))
                        tipoIn = tipo = 68;
                    else if (tipoMensaje == typeof(CancelarSubastaMessage))
                        tipoIn = tipo = 65;
                    break;
                case MOMENTOAUDITORIA.CORRECTO:
                    if (tipoMensaje == typeof(MessageCrearSubasta))
                        tipoIn = tipo = 42;
                    else if (tipoMensaje == typeof(ModificarSubastaMessage))
                        tipoIn = tipo = 63;
                    else if (tipoMensaje == typeof(FinalizarSubastaMessage))
                        tipoIn = tipo = 69;
                    else if (tipoMensaje == typeof(CancelarSubastaMessage))
                        tipoIn = tipo = 66;
                    break;
                case MOMENTOAUDITORIA.LAST_ERROR:
                    if (tipoMensaje == typeof(MessageCrearSubasta))
                        tipoIn = tipo = 43;
                    else if (tipoMensaje == typeof(ModificarSubastaMessage))
                        tipoIn = tipo = 64;
                    else if (tipoMensaje == typeof(FinalizarSubastaMessage))
                        tipoIn = tipo = 70;
                    else if (tipoMensaje == typeof(CancelarSubastaMessage))
                        tipoIn = tipo = 67;
                    break;
            }

            try
            {
                mens = GaleriaContext.Instance.Contexto.TiposAuditoria.
                                Where(ta => ta.Id == tipoIn).Select(ta => ta.Descripcion).First();
            }
            catch (Exception)
            {
                System.Threading.Thread.Sleep(1000);
                mens = GaleriaContext.Instance.Contexto.TiposAuditoria.
                            Where(ta => ta.Id == tipoIn)
                            .Select(ta => ta.Descripcion).First();
            }

            usuario = (int)tipoMensaje.GetProperty("UsuarioId", typeof(int)).GetValue(mensaje, null);
            descripcion = string.Format(mens, new object[] { ((Subasta)(tipoMensaje.GetProperty("Subasta", typeof(Subasta)).GetValue(mensaje, null))).Id });
        }

        private void InsertAuditoriaDB(string command)
        {
            try
            {
                GaleriaContext.Instance.Contexto.ExecuteStoreCommand(command);
            }
            catch (Exception)
            {
                System.Threading.Thread.Sleep(1000);
                GaleriaContext.Instance.Contexto.ExecuteStoreCommand(command);
            }
        }

        public void sendMessagesMQ()
        {
            ActiveMQMapMessage mensaje;
            int intentos;
            NmsTemplate template;
            ConnectionFactory connFactory;
            string destino, logMessage, failMessage = string.Empty;

            connFactory = new ConnectionFactory(ServerConnData.Instance.ActiveMQURL);
            template = new NmsTemplate(connFactory);
            
            while(peticionesMQ.Count > 0)
            {
                lock (this.peticionesMQ)
                {
                    mensaje = peticionesMQ.Dequeue();
                }

                intentos = 0;
                destino = mensaje.Body.GetBool("suscripcion") ? "Suscripciones" : "Pujas";
                logMessage = mensaje.Body.GetBool("suscripcion") ? "Enviando petición de suscripción a la subasta " + mensaje.Body.GetLong("referencia")
                    : "Enviando puja con importe " + mensaje.Body.GetDouble("cantidad") + " sobre la subasta con referencia externa " + mensaje.Body.GetLong("referencia");
                failMessage = mensaje.Body.GetBool("suscripcion") ? "Falló el envío de suscripción a la subasta " + mensaje.Body.GetLong("referencia")
                    : "Falló el envío de la puja con importe " + mensaje.Body.GetDouble("cantidad") + " sobre la subasta con referencia externa " + mensaje.Body.GetLong("referencia");
                mensaje.Body.Remove("suscripcion");
                Utilities.WriteMessageOnGrid(doc, logMessage, DataGridMessage.TIPO_MENSAJE.INFO);

                while (true)
                {
                    try
                    {
                        template.ConvertAndSend(destino, mensaje);
                        break;
                    }
                    catch (Exception)
                    {
                        intentos++;
                        if (intentos == 1)
                            jUDDIManager.Instance.UpdateURL();
                        else if (intentos == 3)
                            break;
                        System.Threading.Thread.Sleep(2000);
                    }
                }

                if (intentos == 3)
                    Utilities.WriteMessageOnGrid(doc, failMessage, DataGridMessage.TIPO_MENSAJE.ERROR);
            }
        }

        private void RollBack(IMessageType mensaje)
        {
            Subasta sub;
            SubastaHasEstadoSubasta est;
            SubastaHasEstadoSubasta aux;

            if (mensaje is MessageCrearSubasta)
            {
                // Deshacemos el cambio de estado...
                sub = ((MessageCrearSubasta)mensaje).Subasta;
                if (sub.EstadoActual != null)
                {
                    est = sub.SubastaHasEstadosSubasta.Where(shs => shs.FechaFin == null).First();
                    sub.SubastaHasEstadosSubasta.Remove(est);
                }
                aux = sub.SubastaHasEstadosSubasta.Where(shs => shs.EstadosSubasta.Estado.Equals("creada")).First();
                aux.FechaFin = null;
                GaleriaContext.Instance.Contexto.SaveChanges();
                // Fin de deshacemos el cambio de estado..
            }
            else if (mensaje is FinalizarSubastaMessage)
            {
                // Deshacemos el cambio de estado...
                sub = ((FinalizarSubastaMessage)mensaje).Subasta;
                if (sub.EstadoActual != null)
                {
                    est = sub.SubastaHasEstadosSubasta.Where(shs => shs.FechaFin == null).First();
                    sub.SubastaHasEstadosSubasta.Remove(est);
                }
                aux = sub.SubastaHasEstadosSubasta.Where(shs => shs.EstadosSubasta.Estado.Equals(((FinalizarSubastaMessage)mensaje).LastEstado)).First();
                aux.FechaFin = null;
                GaleriaContext.Instance.Contexto.SaveChanges();
                // Fin de deshacemos el cambio de estado..
            }
            else if (mensaje is CancelarSubastaMessage)
            {
                // Deshacemos el cambio de estado...
                sub = ((CancelarSubastaMessage)mensaje).Subasta;
                if (sub.EstadoActual != null)
                {
                    est = sub.SubastaHasEstadosSubasta.Where(shs => shs.FechaFin == null).First();
                    sub.SubastaHasEstadosSubasta.Remove(est);
                }
                aux = sub.SubastaHasEstadosSubasta.Where(shs => shs.EstadosSubasta.Estado.Equals(((CancelarSubastaMessage)mensaje).LastEstado)).First();
                aux.FechaFin = null;
                GaleriaContext.Instance.Contexto.SaveChanges();
                // Fin de deshacemos el cambio de estado..
            }
            else if (mensaje is ModificarSubastaMessage)
            {
                // Tiramos los cambios para atrás...
                sub = ((ModificarSubastaMessage)mensaje).Subasta;
                sub.MaximoPujasPostor = ((ModificarSubastaMessage)mensaje).LastMaxPujas;
                sub.FechaLimite = ((ModificarSubastaMessage)mensaje).LastDateFin;
                GaleriaContext.Instance.Contexto.SaveChanges();
            }
        }

        public void addMessageWS(IMessageType message)
        {
            lock (this.peticiones)
            {
                peticiones.Enqueue(message);
            }
        }

        public void addMessageMQQueue(ActiveMQMapMessage message)
        {
            lock (this.peticionesMQ)
            {
                peticionesMQ.Enqueue(message);
            }
        }

        public bool HayPeticionesPendientes()
        {
            return peticiones.Count > 0;
        }

        public bool HayPeticionesPendientesMQ()
        {
            return peticionesMQ.Count > 0;
        }

        public void addMessageMQ(ActiveMQMapMessage message)
        {
            if (!activeMQEntrada.ContainsKey(message.Body.GetLong("referencia")))
                activeMQEntrada.Add(message.Body.GetLong("referencia"), message);
        }

        public bool activeMQContainsKey(long key)
        {
            return activeMQEntrada.ContainsKey(key);
        }

        public void deleteMessageMQ(long key)
        {
            if (activeMQContainsKey(key))
                activeMQEntrada.Remove(key);
        }
    }
}
